Skip to content

Refactor badges update job: modularize handlers, introduce base class, and add tests#3040

Merged
arkid15r merged 29 commits intoOWASP:mainfrom
mrkeshav-05:refactor/nest-badges
Jan 10, 2026
Merged

Refactor badges update job: modularize handlers, introduce base class, and add tests#3040
arkid15r merged 29 commits intoOWASP:mainfrom
mrkeshav-05:refactor/nest-badges

Conversation

@mrkeshav-05
Copy link
Contributor

Proposed change

This PR modularises the badge update with BaseBadgeHandler to share logic and separate handler files for each badge type.

Resolves: #3019

Checklist

  • Required: I read and followed the contributing guidelines
  • Required: I ran make check-test locally and all tests passed
  • I used AI for code, documentation, or tests in this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Summary by CodeRabbit

  • Refactor

    • Reworked badge synchronization into a modular base command with separate handlers for OWASP Staff and Project Leader badges.
  • Tests

    • Added unit tests covering the new badge sync framework and each specialized badge handler, including success and error paths.
  • Chores

    • Updated build/make targets to run the new per-badge sync tasks.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Refactors badge-sync into a reusable BaseBadgeCommand, removes the old monolithic nest_update_badges.py, adds two specific commands (nest_update_staff_badges.py, nest_update_project_leader_badges.py), updates bulk_save signatures on models, and replaces/adds corresponding tests and Makefile targets.

Changes

Cohort / File(s) Summary
Base class & model APIs
backend/apps/nest/management/commands/base_badge_command.py, backend/apps/nest/models/user_badge.py, backend/apps/github/models/organization.py
Add BaseBadgeCommand (abstract orchestration, metadata, get_eligible_users()), add UserBadge.bulk_save(...), and make Organization.bulk_save(..., fields=None) forward fields.
New badge commands
backend/apps/nest/management/commands/nest_update_staff_badges.py, backend/apps/nest/management/commands/nest_update_project_leader_badges.py
New commands extending BaseBadgeCommand; staff command filters User by is_owasp_staff, project-leader command selects leader Users via EntityMemberProject.
Removed legacy command
backend/apps/nest/management/commands/nest_update_badges.py
Deleted the previous monolithic badge-sync management command and its helpers/constants.
Makefile targets
backend/apps/nest/Makefile
Replace single nest-update-badges invocation with per-badge targets (nest-update-staff-badges, nest-update-project-leader-badges) and an aggregate nest-update-badges that depends on them.
Tests removed
backend/tests/apps/nest/management/commands/nest_update_badges_test.py
Remove legacy test suite for the deleted monolithic command.
Tests added/updated
backend/tests/apps/nest/management/commands/base_badge_command_test.py, backend/tests/apps/nest/management/commands/nest_update_staff_badges_test.py, backend/tests/apps/nest/management/commands/nest_update_project_leader_badges_test.py, backend/tests/apps/github/models/organization_test.py
Add unit tests for BaseBadgeCommand and both new badge commands (metadata, success path, error propagation); update org bulk_save test to include fields=None arg.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #2949: Modifies OWASP badge synchronization logic and introduces project-leader handling — directly overlaps with splitting badge handlers here.
  • PR #2047: Touches Nest badge sync area and UserBadge model/related migrations; likely overlaps with model/API changes.
  • PR #2063: Introduces EntityMember usage and leader-related changes used by the new project-leader command.

Suggested reviewers

  • kasya
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main refactoring work: introducing a base class, modularizing handlers, and adding tests for the badge update job.
Description check ✅ Passed The description mentions the key change (modularizing with BaseBadgeHandler) and references the linked issue #3019, directly relating to the changeset.
Linked Issues check ✅ Passed The PR successfully addresses all requirements from issue #3019: separate handler files per badge type [nest_update_project_leader_badges.py, nest_update_staff_badges.py], base class implementation [base_badge_command.py], comprehensive test coverage, and documented code structure.
Out of Scope Changes check ✅ Passed Changes to Organization.bulk_save and UserBadge.bulk_save are supporting enhancements to enable efficient bulk operations in the new badge command handlers, which is within scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf3a1f7 and 67c1f71.

📒 Files selected for processing (1)
  • backend/apps/nest/models/user_badge.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/nest/models/user_badge.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/nest/models/user_badge.py
🧬 Code graph analysis (1)
backend/apps/nest/models/user_badge.py (3)
backend/apps/github/models/organization.py (1)
  • bulk_save (89-91)
backend/apps/github/models/user.py (1)
  • bulk_save (167-169)
backend/apps/common/models.py (1)
  • BulkSaveModel (10-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run frontend e2e tests
🔇 Additional comments (1)
backend/apps/nest/models/user_badge.py (1)

52-55: LGTM! Consistent with established codebase pattern.

The bulk_save static method correctly follows the same pattern used in User and Organization models. The delegation to BulkSaveModel.bulk_save() is properly structured, the signature appropriately narrows the base implementation for UserBadge-specific usage, and the type: ignore[override] comment correctly suppresses the intentional signature deviation.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
backend/apps/nest/badges/project_leader_badge.py (1)

28-47: Consider caching the ContentType lookup.

The eligibility logic is correct and comprehensive. The filters appropriately identify active, reviewed project leaders with linked user accounts.

For improved efficiency, consider caching ContentType.objects.get_for_model(Project) as a class attribute, since it's called every time get_eligible_users() runs and always returns the same value.

🔎 Suggested optimization

At the class level, add:

class OWASPProjectLeaderBadgeHandler(BaseBadgeHandler):
    """Handler for managing the OWASP Project Leader badge.

    This badge is awarded to users who are active and reviewed leaders
    of OWASP projects. It uses the EntityMember model for users with the LEADER role.
    """

    name = "OWASP Project Leader"
    description = "Official OWASP Project Leader"
    css_class = "fa-user-shield"
    weight = 90
    
    # Cache the content type at class level
    _project_content_type = None
    
    @classmethod
    def get_project_content_type(cls):
        if cls._project_content_type is None:
            cls._project_content_type = ContentType.objects.get_for_model(Project)
        return cls._project_content_type

Then update the method:

     def get_eligible_users(self) -> QuerySet[User]:
         # Get IDs of users who are active and reviewed project leaders
         leader_ids = EntityMember.objects.filter(
-            entity_type=ContentType.objects.get_for_model(Project),
+            entity_type=self.get_project_content_type(),
             role=EntityMember.Role.LEADER,
             is_active=True,
             is_reviewed=True,
             member__isnull=False,
         ).values_list("member_id", flat=True)
         return User.objects.filter(id__in=leader_ids).distinct()
backend/apps/nest/badges/base.py (1)

75-81: Consider bulk operations for better performance.

The current implementation uses individual get_or_create and save calls in a loop. For large user bases, this could be optimized using bulk operations to reduce database round-trips.

💡 Potential optimization approach
# Get existing UserBadge records
existing_user_badges = UserBadge.objects.filter(
    user__in=users_to_add,
    badge=badge
).select_related('user')

existing_map = {ub.user_id: ub for ub in existing_user_badges}

# Prepare lists for bulk operations
to_create = []
to_update = []

for user in users_to_add:
    if user.id in existing_map:
        user_badge = existing_map[user.id]
        if not user_badge.is_active:
            user_badge.is_active = True
            to_update.append(user_badge)
    else:
        to_create.append(UserBadge(user=user, badge=badge, is_active=True))

# Bulk create and update
if to_create:
    UserBadge.objects.bulk_create(to_create)
if to_update:
    UserBadge.objects.bulk_update(to_update, ['is_active'])

added_count = len(to_create) + len(to_update)

Note: This optimization is optional and can be deferred if the current performance is acceptable.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e38d6a and 2a6afd6.

📒 Files selected for processing (5)
  • backend/apps/nest/badges/__init__.py
  • backend/apps/nest/badges/base.py
  • backend/apps/nest/badges/project_leader_badge.py
  • backend/apps/nest/badges/staff_badge.py
  • backend/apps/nest/management/commands/nest_update_badges.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-01T04:15:32.151Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 1823
File: frontend/__tests__/e2e/pages/Login.spec.ts:28-34
Timestamp: 2025-08-01T04:15:32.151Z
Learning: In the OWASP Nest project, the login page (/auth/login) handles only authentication (GitHub OAuth) and does not differentiate between OWASP staff and non-staff users. The role-based access control using the is_owasp_staff field happens after authentication in downstream components like DashboardWrapper and ProjectsWrapper, not during the login process itself.

Applied to files:

  • backend/apps/nest/badges/staff_badge.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_badges.py
🧬 Code graph analysis (4)
backend/apps/nest/badges/__init__.py (3)
backend/apps/github/api/internal/nodes/user.py (1)
  • badges (37-51)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (16-47)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (13-28)
backend/apps/nest/management/commands/nest_update_badges.py (4)
backend/apps/github/api/internal/nodes/user.py (1)
  • badges (37-51)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (16-47)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (13-28)
backend/apps/nest/badges/base.py (1)
  • process (47-97)
backend/apps/nest/badges/project_leader_badge.py (2)
backend/apps/nest/badges/base.py (2)
  • BaseBadgeHandler (13-97)
  • get_eligible_users (26-30)
backend/apps/owasp/models/entity_member.py (2)
  • EntityMember (10-112)
  • Role (13-16)
backend/apps/nest/badges/base.py (3)
backend/apps/nest/models/user_badge.py (1)
  • UserBadge (10-50)
backend/apps/nest/badges/project_leader_badge.py (1)
  • get_eligible_users (28-47)
backend/apps/nest/badges/staff_badge.py (1)
  • get_eligible_users (21-28)
🪛 Ruff (0.14.10)
backend/apps/nest/badges/__init__.py

6-9: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

backend/apps/nest/badges/staff_badge.py

22-27: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


25-25: Missing blank line after last section ("Returns")

Add blank line after "Returns"

(D413)

backend/apps/nest/badges/project_leader_badge.py

29-37: No blank lines allowed after function docstring (found 1)

Remove blank line(s) after function docstring

(D202)


29-37: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


35-35: Missing blank line after last section ("Returns")

Add blank line after "Returns"

(D413)

backend/apps/nest/badges/base.py

1-1: Missing docstring in public module

(D100)


21-21: Missing docstring in __init__

(D107)


27-29: One-line docstring should fit on one line

Reformat to one line

(D200)


27-29: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


30-30: Unnecessary pass statement

Remove unnecessary pass

(PIE790)


32-32: Missing docstring in public method

(D102)


40-40: First line of docstring should be in imperative mood: "Helper to log to both file logger and stdout if available."

(D401)


48-53: 1 blank line required between summary line and description

(D205)


48-53: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


48-53: First line of docstring should be in imperative mood: "Main execution method to sync the badge."

(D401)


55-55: Avoid specifying long messages outside the exception class

(TRY003)


55-55: Exception must not use a string literal, assign to variable first

Assign to variable; remove string literal

(EM101)


97-97: No newline at end of file

Add trailing newline

(W292)

🔇 Additional comments (5)
backend/apps/nest/badges/staff_badge.py (2)

13-19: LGTM!

The class definition and badge metadata are correctly structured. The weight of 100 appropriately positions the staff badge with high priority.


21-28: LGTM!

The eligibility logic correctly filters users by the is_owasp_staff field, which aligns with the project's role-based access control approach.

backend/apps/nest/badges/__init__.py (1)

1-9: LGTM!

The package initialization correctly exposes the two badge handlers, establishing a clean public API for the badges module.

backend/apps/nest/badges/project_leader_badge.py (1)

16-26: LGTM!

The class definition and badge metadata are well-structured. The weight of 90 appropriately positions the project leader badge below the staff badge.

backend/apps/nest/management/commands/nest_update_badges.py (1)

20-37: Excellent refactoring with proper error isolation!

The new pluggable handler architecture cleanly separates concerns and improves maintainability. The per-handler try/except ensures that a failure in one badge type doesn't prevent other badges from being processed. Error logging and styled output align with Django management command best practices.

Based on learnings, the use of self.stdout.write and self.style.SUCCESS/ERROR is appropriate for Django management commands.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 25, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
backend/tests/apps/nest/badges/staff_badge_test.py (1)

10-18: Add trailing newline and consider asserting the return value.

The test verifies the filter is called correctly but doesn't assert that get_eligible_users() returns the expected queryset. Consider adding an assertion for completeness.

🔎 Proposed enhancement
     @patch("apps.nest.badges.staff_badge.User")
     def test_get_eligible_users_filters_staff(self, mock_user_model):
         """Test that get_eligible_users filters for is_owasp_staff=True."""
         handler = OWASPStaffBadgeHandler()
-        handler.get_eligible_users()
+        result = handler.get_eligible_users()

         mock_user_model.objects.filter.assert_called_once_with(is_owasp_staff=True)
+        assert result == mock_user_model.objects.filter.return_value
+
backend/tests/apps/nest/management/commands/nest_update_badges_test.py (1)

76-86: Add trailing newline.

The test correctly verifies exception logging with stack traces. Minor: add a trailing newline at end of file.

🔎 Proposed fix
         mock_handlers_dict.keys.return_value = ["staff"]
         call_command("nest_update_badges", stdout=StringIO())
         mock_logger.exception.assert_called_with("Error processing badge %s", "OWASP Staff")
+
backend/tests/apps/nest/badges/project_leader_badge_test.py (2)

7-8: Remove unused import.

EntityMember is imported but not used in the test. The assertion on line 38 uses mock_entity_member.Role.LEADER (the patched mock), not the actual import.

🔎 Proposed fix
 from apps.nest.badges.project_leader_badge import OWASPProjectLeaderBadgeHandler
-from apps.owasp.models.entity_member import EntityMember

43-44: Add trailing newline.

🔎 Proposed fix
         mock_user_model.objects.filter.assert_called_once_with(id__in=[101, 102])
         mock_user_model.objects.filter.return_value.distinct.assert_called_once()
+
backend/apps/nest/management/commands/nest_update_badges.py (1)

43-52: Consider differentiating success message when handlers fail.

The "User badges sync completed" message prints unconditionally, even if all handlers failed. This could be misleading in error scenarios.

🔎 Proposed enhancement
+        failed_count = 0
         for handler_class in handlers_to_run:
             try:
                 self.stdout.write(f"Processing badge: {handler_class.name}")
                 handler = handler_class(stdout=self.stdout, style=self.style)
                 handler.process()
             except Exception:
                 logger.exception("Error processing badge %s", handler_class.name)
                 self.stdout.write(self.style.ERROR(f"Failed to update {handler_class.name}"))
+                failed_count += 1

-        self.stdout.write(self.style.SUCCESS("User badges sync completed"))
+        if failed_count:
+            self.stdout.write(
+                self.style.WARNING(f"User badges sync completed with {failed_count} failure(s)")
+            )
+        else:
+            self.stdout.write(self.style.SUCCESS("User badges sync completed"))
backend/tests/apps/nest/badges/base_test.py (2)

11-19: Add blank line after class docstring.

Per PEP 257 convention (flagged by static analysis D204).

🔎 Proposed fix
 class ConcreteTestHandler(BaseBadgeHandler):
     """Concrete implementation of BaseBadgeHandler for testing purposes."""
+
     name = "Test Badge"
     description = "Test Description"

75-95: Add trailing newline; tests look good.

The revocation test correctly verifies the bulk update flow. Add trailing newline at end of file.

🔎 Proposed fix
         mock_revocation_qs.update.assert_called_with(is_active=False)
         self.assertIn("Removed 'Test Badge' badge from 5 users", self.out.getvalue())
+
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a6afd6 and 1e54fb8.

📒 Files selected for processing (10)
  • backend/apps/nest/Makefile
  • backend/apps/nest/badges/__init__.py
  • backend/apps/nest/badges/base.py
  • backend/apps/nest/badges/project_leader_badge.py
  • backend/apps/nest/badges/staff_badge.py
  • backend/apps/nest/management/commands/nest_update_badges.py
  • backend/tests/apps/nest/badges/base_test.py
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
  • backend/tests/apps/nest/badges/staff_badge_test.py
  • backend/tests/apps/nest/management/commands/nest_update_badges_test.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/apps/nest/badges/staff_badge.py
  • backend/apps/nest/badges/init.py
  • backend/apps/nest/badges/base.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-06T19:03:01.985Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2223
File: backend/apps/owasp/models/common.py:213-232
Timestamp: 2025-09-06T19:03:01.985Z
Learning: In the OWASP Nest project, the get_leaders_emails() method in RepositoryBasedEntityModel is designed to only capture leaders with mailto: links from leaders.md files, intentionally ignoring plain text names without email addresses. The current regex implementation works correctly for the intended behavior as validated by comprehensive test cases.

Applied to files:

  • backend/apps/nest/badges/project_leader_badge.py
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/nest/management/commands/nest_update_badges_test.py
  • backend/apps/nest/management/commands/nest_update_badges.py
🧬 Code graph analysis (5)
backend/apps/nest/badges/project_leader_badge.py (3)
backend/apps/nest/badges/base.py (2)
  • BaseBadgeHandler (15-86)
  • get_eligible_users (29-30)
backend/apps/owasp/models/entity_member.py (2)
  • EntityMember (10-112)
  • Role (13-16)
backend/apps/nest/badges/staff_badge.py (1)
  • get_eligible_users (17-24)
backend/tests/apps/nest/management/commands/nest_update_badges_test.py (3)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
backend/apps/nest/badges/base.py (1)
  • process (48-86)
backend/tests/apps/github/management/commands/github_update_related_organizations_test.py (1)
  • mock_logger (18-22)
backend/tests/apps/nest/badges/base_test.py (1)
backend/apps/nest/badges/base.py (3)
  • BaseBadgeHandler (15-86)
  • get_eligible_users (29-30)
  • process (48-86)
backend/apps/nest/management/commands/nest_update_badges.py (5)
backend/apps/github/api/internal/nodes/user.py (1)
  • badges (37-51)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (12-34)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (9-24)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
backend/apps/nest/badges/base.py (1)
  • process (48-86)
backend/tests/apps/nest/badges/project_leader_badge_test.py (4)
backend/apps/nest/badges/project_leader_badge.py (1)
  • get_eligible_users (20-34)
backend/apps/owasp/models/entity_member.py (2)
  • EntityMember (10-112)
  • Role (13-16)
backend/apps/nest/badges/base.py (1)
  • get_eligible_users (29-30)
backend/tests/apps/nest/badges/base_test.py (1)
  • get_eligible_users (18-19)
🪛 Ruff (0.14.10)
backend/tests/apps/nest/badges/staff_badge_test.py

1-1: File backend/tests/apps/nest/badges/staff_badge_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


18-18: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/nest/badges/base_test.py

1-1: File backend/tests/apps/nest/badges/base_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


12-12: 1 blank line required after class docstring

Insert 1 blank line after class docstring

(D204)


50-50: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


73-73: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


95-95: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


95-95: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/nest/badges/project_leader_badge_test.py

1-1: File backend/tests/apps/nest/badges/project_leader_badge_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


44-44: No newline at end of file

Add trailing newline

(W292)

🔇 Additional comments (7)
backend/apps/nest/Makefile (1)

1-3: LGTM!

The addition of $(ARGS) enables passing the new --handler argument to the management command, aligning with the refactored handler-driven architecture.

backend/tests/apps/nest/management/commands/nest_update_badges_test.py (2)

13-34: LGTM!

Good test coverage for the all-handlers scenario. The mocking approach correctly isolates the handler instantiation and process calls.


52-74: LGTM!

Excellent test for error resilience - verifies that a failing handler doesn't prevent subsequent handlers from executing, and that appropriate error messages are written to stdout.

backend/apps/nest/badges/project_leader_badge.py (1)

12-34: LGTM!

The implementation correctly queries for active, reviewed project leaders with linked user accounts. The distinct() is a good defensive measure.

One minor consideration: ContentType.objects.get_for_model(Project) is called on every invocation. Django caches ContentType lookups internally, so this is fine for the current use case.

backend/apps/nest/management/commands/nest_update_badges.py (1)

17-20: LGTM!

Clean handler registry pattern that makes adding new badge handlers straightforward. The keys provide intuitive CLI identifiers.

backend/tests/apps/nest/badges/base_test.py (2)

28-50: LGTM!

Good coverage of badge creation flow. Verifies get_or_create is called with correct defaults and that the creation message is logged.


52-73: LGTM!

Thorough test of the badge assignment flow - correctly verifies UserBadge creation, activation toggle, and save with update_fields.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/tests/apps/nest/badges/staff_badge_test.py (1)

1-19: Consider adding __init__.py for explicit test package structure.

The test directory backend/tests/apps/nest/badges/ would benefit from an explicit __init__.py file to ensure consistent test discovery across different test runners and Python environments, though implicit namespace packages work in Python 3.3+.

📝 Create `__init__.py` in test directories

Create an empty backend/tests/apps/nest/badges/__init__.py:

"""Tests for nest badge handlers."""

Apply this to all intermediate test directories if not already present:

  • backend/tests/apps/nest/badges/__init__.py

Test implementation looks solid.

The test correctly verifies that get_eligible_users calls User.objects.filter with the expected parameter and returns the filtered queryset.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e54fb8 and 4bab2d4.

📒 Files selected for processing (4)
  • backend/apps/nest/management/commands/nest_update_badges.py
  • backend/tests/apps/nest/badges/base_test.py
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
  • backend/tests/apps/nest/badges/staff_badge_test.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_badges.py
🧬 Code graph analysis (3)
backend/apps/nest/management/commands/nest_update_badges.py (3)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (12-34)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (9-24)
backend/apps/nest/badges/base.py (1)
  • process (48-86)
backend/tests/apps/nest/badges/base_test.py (3)
backend/apps/github/api/internal/nodes/user.py (1)
  • badges (37-51)
backend/apps/nest/badges/base.py (3)
  • BaseBadgeHandler (15-86)
  • get_eligible_users (29-30)
  • process (48-86)
backend/apps/nest/badges/staff_badge.py (1)
  • get_eligible_users (17-24)
backend/tests/apps/nest/badges/project_leader_badge_test.py (2)
backend/apps/nest/badges/project_leader_badge.py (2)
  • OWASPProjectLeaderBadgeHandler (12-34)
  • get_eligible_users (20-34)
backend/apps/owasp/models/entity_member.py (1)
  • Role (13-16)
🪛 Ruff (0.14.10)
backend/tests/apps/nest/badges/base_test.py

1-1: File backend/tests/apps/nest/badges/base_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


51-51: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


74-74: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


96-96: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)

backend/tests/apps/nest/badges/project_leader_badge_test.py

1-1: File backend/tests/apps/nest/badges/project_leader_badge_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/nest/badges/staff_badge_test.py

1-1: File backend/tests/apps/nest/badges/staff_badge_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

🔇 Additional comments (7)
backend/tests/apps/nest/badges/project_leader_badge_test.py (1)

1-43: Comprehensive test coverage for project leader badge logic.

The test thoroughly validates the multi-step query process:

  • ContentType resolution for the Project model
  • EntityMember filtering with correct role, status, and relationship checks
  • User filtering by leader IDs with distinct() applied

The mock setup and assertions align well with the handler implementation.

backend/apps/nest/management/commands/nest_update_badges.py (2)

17-30: Well-designed pluggable handler registry.

The BADGE_HANDLERS dictionary provides a clean extension point for adding new badge types. The --handler argument with choices validation ensures only valid handlers can be selected, while allowing all handlers to run when omitted.


32-61: Robust handler execution with proper error handling.

The implementation correctly:

  • Selects handlers based on CLI argument
  • Instantiates each handler with stdout and style for output formatting
  • Isolates failures with per-handler try/except blocks so one failure doesn't abort the entire run
  • Uses logger.exception to capture full tracebacks
  • Reports final status with failure count

The use of self.stdout.write() and self.style methods aligns with Django management command conventions. Based on learnings, this is the preferred approach over print().

backend/tests/apps/nest/badges/base_test.py (4)

11-27: Solid test infrastructure for abstract base class.

The ConcreteTestHandler provides a minimal concrete implementation suitable for testing the base class logic, and the setUp method correctly initializes output capture for verifying _log calls.


29-51: Badge creation test correctly validates core behavior.

The test verifies that process() calls Badge.objects.get_or_create with the correct badge name and defaults (description, css_class, weight), and confirms the creation message is logged.


53-74: Badge assignment test validates activation logic.

The test correctly verifies that process():

  • Creates or retrieves UserBadge for eligible users
  • Activates inactive badges by setting is_active=True
  • Saves with update_fields for efficiency
  • Logs the count of added badges

76-96: Badge revocation test validates cleanup logic.

The test correctly verifies that process():

  • Identifies UserBadge records for users no longer eligible
  • Bulk updates them with is_active=False
  • Logs the count of revoked badges

The use of assertIn is appropriate for SimpleTestCase (unittest-based tests). The static analysis suggestion to use assert instead applies to pytest-style tests, not Django's unittest framework.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/tests/apps/nest/badges/base_test.py (1)

54-77: Consider mocking the revocation branch for better test isolation.

The test correctly validates the badge addition flow. However, the revocation branch (UserBadge.objects.filter(...).exclude(...)) is not explicitly mocked. Due to MagicMock's default behavior, this branch will execute with a truthy count() value, causing unintended log output during the test.

While the test passes because the assertion only checks for the "Added" message, explicitly mocking the revocation path would make the test more precise.

🔎 Optional: Add explicit mock for revocation path
         mock_user_badge = MagicMock()
         mock_user_badge.is_active = False
         mock_user_badge_model.objects.get_or_create.return_value = (mock_user_badge, False)
+
+        # Mock revocation path to return no users to revoke
+        mock_revocation_qs = MagicMock()
+        mock_revocation_qs.count.return_value = 0
+        mock_user_badge_model.objects.filter.return_value.exclude.return_value = mock_revocation_qs
+
         self.handler.process()
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bab2d4 and e7132f6.

📒 Files selected for processing (5)
  • backend/apps/nest/management/commands/nest_update_badges.py
  • backend/tests/apps/nest/badges/__init__.py
  • backend/tests/apps/nest/badges/base_test.py
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
  • backend/tests/apps/nest/badges/staff_badge_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_badges.py
🧬 Code graph analysis (3)
backend/apps/nest/management/commands/nest_update_badges.py (3)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (12-34)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (9-24)
backend/apps/nest/badges/base.py (1)
  • process (48-86)
backend/tests/apps/nest/badges/base_test.py (1)
backend/apps/nest/badges/base.py (3)
  • BaseBadgeHandler (15-86)
  • get_eligible_users (29-30)
  • process (48-86)
backend/tests/apps/nest/badges/staff_badge_test.py (4)
backend/apps/nest/badges/staff_badge.py (2)
  • OWASPStaffBadgeHandler (9-24)
  • get_eligible_users (17-24)
backend/tests/apps/nest/badges/base_test.py (1)
  • get_eligible_users (19-20)
backend/apps/nest/badges/base.py (1)
  • get_eligible_users (29-30)
backend/apps/nest/api/internal/nodes/user.py (1)
  • is_owasp_staff (14-16)
🔇 Additional comments (6)
backend/tests/apps/nest/badges/staff_badge_test.py (1)

10-18: LGTM!

Clean and focused test that correctly validates the core filtering logic. The patch target path is correct (patching where the User is imported in the module under test), and the assertion properly verifies both the filter call arguments and the return value.

backend/apps/nest/management/commands/nest_update_badges.py (2)

44-52: Robust error handling with continuation.

Good pattern: catching exceptions per-handler allows other handlers to proceed even if one fails. Using logger.exception captures the full traceback for debugging.

One minor consideration: if a handler class doesn't define name (violating the base class contract), accessing handler_class.name on line 46 could raise AttributeError before process() is called. However, since the handlers are well-defined in this PR and the base class enforces name via its contract, this is acceptable.


32-41: LGTM!

Clean implementation of the handler selection logic. Using self.stdout.write aligns with Django management command conventions. Based on learnings, this approach improves testability compared to using print().

backend/tests/apps/nest/badges/base_test.py (3)

30-52: LGTM!

Good test coverage for the badge creation path. Correctly verifies:

  • get_or_create is called with the expected name and defaults
  • The "Created badge" log message appears when a new badge is created

79-101: LGTM!

Good test coverage for the revocation path. The mock setup correctly chains filter().exclude() and verifies the update(is_active=False) call.


11-20: LGTM!

Clean concrete test handler implementation that satisfies the abstract base class contract. Defining all required attributes (name, description, css_class, weight) ensures the process() method can execute without raising validation errors.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 25, 2025
@mrkeshav-05 mrkeshav-05 marked this pull request as ready for review December 25, 2025 22:14
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I expected it to have a management command base and derived per badge management commands. What do you think about this refactoring?

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 2, 2026
@mrkeshav-05 mrkeshav-05 requested a review from arkid15r January 2, 2026 15:51
]

if new_badges:
UserBadge.objects.bulk_create(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class has BulkSaveModel functionality.

added_count = len(new_badges)

if added_count > 0:
user_word = "user" if added_count == 1 else "users"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not consistent with existing cases.


added_count = len(new_badges)

if added_count > 0:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't learn from previous review rounds.

Suggested change
if added_count > 0:
if added_count:

unique_fields=["user", "badge"],
)

added_count = len(new_badges)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can combine those ifs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26d10e9 and 5699b90.

📒 Files selected for processing (1)
  • backend/apps/nest/management/commands/base_badge_command.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
🧬 Code graph analysis (1)
backend/apps/nest/management/commands/base_badge_command.py (3)
backend/apps/nest/models/user_badge.py (1)
  • UserBadge (10-50)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • get_eligible_users (20-28)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • get_eligible_users (17-18)
🔇 Additional comments (8)
backend/apps/nest/management/commands/base_badge_command.py (8)

1-14: LGTM: Clean imports and setup.

The imports and logger configuration follow Django best practices. The use of ABC for the abstract base class is appropriate for this modular design.


16-23: LGTM: Well-structured base class design.

The class-level attributes provide a clean interface for subclasses to declare badge metadata. The use of optional types (str | None) correctly indicates these must be overridden by concrete implementations.


24-26: LGTM: Abstract method enforces the template pattern.

The abstract get_eligible_users() method correctly forces subclasses to implement badge-specific eligibility logic, which is the core goal of this refactoring.


28-30: LGTM: Logging follows Django conventions.

The dual logging to both logger.info and self.stdout.write is appropriate for Django management commands, aligning with retrieved learnings about command output patterns.


32-37: LGTM: Defensive validation.

The validation ensures subclasses properly configure badge_name, providing a clear error message if not. The initial sync message provides good user feedback.


39-56: LGTM: Metadata syncing correctly implemented.

The code now properly updates existing badge metadata (lines 52-55) in addition to setting defaults on creation. This ensures the "sync" command truly synchronizes badge properties on every run. The use of update_fields is efficient. ✅ Addresses past review feedback.


78-88: LGTM: Badge removal logic is correct.

The query correctly identifies badges to deactivate, and the bulk update with is_active=False is efficient. Pluralization is handled properly for user feedback.


89-93: LGTM: Proper exception handling and success messaging.

The exception handling logs full details (with traceback via logger.exception) while providing styled output to users. Re-raising the exception allows calling code to handle failures appropriately.

@mrkeshav-05 mrkeshav-05 force-pushed the refactor/nest-badges branch from 5699b90 to a5dffd7 Compare January 2, 2026 20:54
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 2, 2026
@mrkeshav-05 mrkeshav-05 requested a review from arkid15r January 2, 2026 21:00
@arkid15r arkid15r enabled auto-merge January 10, 2026 03:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @backend/apps/nest/models/user_badge.py:
- Around line 52-55: The docstring on the static method bulk_save incorrectly
says "Bulk save organizations."; update the docstring for bulk_save in the
UserBadge class to accurately describe its behavior (e.g., "Bulk save user
badges.") so it references user badges rather than organizations; ensure this
change is applied to the bulk_save method that calls
BulkSaveModel.bulk_save(UserBadge, user_badges, fields=fields).
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5dffd7 and cf3a1f7.

⛔ Files ignored due to path filters (1)
  • backend/generated_videos/community_snapshot_video_2025/2025_snapshot.mkv is excluded by !**/*.mkv
📒 Files selected for processing (9)
  • backend/apps/github/models/organization.py
  • backend/apps/nest/Makefile
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badges.py
  • backend/apps/nest/management/commands/nest_update_staff_badges.py
  • backend/apps/nest/models/user_badge.py
  • backend/tests/apps/github/models/organization_test.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badges_test.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badges_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/nest/management/commands/base_badge_command.py
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/nest/models/user_badge.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badges.py
  • backend/tests/apps/github/models/organization_test.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badges_test.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badges_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badges.py
  • backend/apps/github/models/organization.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/nest/models/user_badge.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badges.py
  • backend/tests/apps/github/models/organization_test.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badges_test.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badges_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badges.py
  • backend/apps/github/models/organization.py
📚 Learning: 2025-11-23T11:37:26.253Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2606
File: backend/apps/api/rest/v0/project.py:43-48
Timestamp: 2025-11-23T11:37:26.253Z
Learning: In the OWASP Nest backend, `entity_leaders` is a `property` method defined in `RepositoryBasedEntityModel` (backend/apps/owasp/models/common.py) that returns a dynamically constructed QuerySet. It cannot be prefetched using standard `prefetch_related()` because Django's prefetch mechanism only works on model fields and relations, not property methods.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_project_leader_badges.py
📚 Learning: 2025-09-06T19:03:01.985Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2223
File: backend/apps/owasp/models/common.py:213-232
Timestamp: 2025-09-06T19:03:01.985Z
Learning: In the OWASP Nest project, the get_leaders_emails() method in RepositoryBasedEntityModel is designed to only capture leaders with mailto: links from leaders.md files, intentionally ignoring plain text names without email addresses. The current regex implementation works correctly for the intended behavior as validated by comprehensive test cases.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_project_leader_badges.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_project_leader_badges.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badges_test.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badges_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badges.py
🧬 Code graph analysis (4)
backend/apps/nest/models/user_badge.py (3)
backend/apps/github/models/organization.py (1)
  • bulk_save (89-91)
backend/apps/github/models/user.py (1)
  • bulk_save (167-169)
backend/apps/common/models.py (1)
  • BulkSaveModel (10-34)
backend/tests/apps/github/models/organization_test.py (1)
backend/apps/github/models/organization.py (1)
  • Organization (20-115)
backend/tests/apps/nest/management/commands/nest_update_staff_badges_test.py (2)
backend/apps/nest/management/commands/nest_update_staff_badges.py (1)
  • Command (9-18)
backend/tests/apps/nest/management/commands/nest_update_project_leader_badges_test.py (3)
  • test_has_correct_metadata (14-16)
  • test_command_runs (24-44)
  • test_handles_errors (52-59)
backend/apps/nest/management/commands/nest_update_staff_badges.py (2)
backend/apps/nest/management/commands/base_badge_command.py (2)
  • BaseBadgeCommand (17-90)
  • get_eligible_users (26-27)
backend/apps/nest/api/internal/nodes/user.py (1)
  • is_owasp_staff (14-16)
🪛 checkmake (0.2.2)
backend/apps/nest/Makefile

[warning] 1-1: Missing required phony target "all"

(minphony)


[warning] 1-1: Missing required phony target "clean"

(minphony)


[warning] 1-1: Missing required phony target "test"

(minphony)

🔇 Additional comments (10)
backend/tests/apps/github/models/organization_test.py (1)

15-15: LGTM!

The test assertion correctly verifies that Organization.bulk_save forwards the fields parameter to BulkSaveModel.bulk_save.

backend/apps/github/models/organization.py (1)

89-91: LGTM!

The addition of the fields parameter enables selective field updates during bulk operations while maintaining backward compatibility. The implementation correctly forwards the parameter to BulkSaveModel.bulk_save.

backend/tests/apps/nest/management/commands/nest_update_staff_badges_test.py (1)

1-46: LGTM!

The test suite provides comprehensive coverage of the staff badges command:

  • Metadata verification ensures correct badge configuration
  • Execution test validates the command's happy path with appropriate mocking
  • Error handling test confirms exceptions propagate correctly

The use of SimpleTestCase is appropriate since all dependencies are mocked, and the testing pattern aligns well with the project leader badges tests.

backend/apps/nest/Makefile (1)

1-11: LGTM!

The refactoring successfully modularizes badge updates into separate targets while maintaining backward compatibility through the aggregate nest-update-badges target. This design facilitates running individual badge updates independently and simplifies adding new badge types in the future.

The static analysis warnings about missing phony targets (all, clean, test) are false positives—this appears to be a partial Makefile included by a parent Makefile that defines those targets.

backend/apps/nest/management/commands/nest_update_staff_badges.py (1)

9-15: Clean implementation of badge metadata.

The command properly extends BaseBadgeCommand and defines all required badge metadata. The weight of 100 (higher than project leader's 90) appropriately reflects the badge hierarchy.

backend/apps/nest/management/commands/nest_update_project_leader_badges.py (2)

12-18: LGTM! Badge metadata is properly configured.

The command correctly extends BaseBadgeCommand with appropriate metadata. The weight of 90 properly positions project leaders in the badge hierarchy below staff (100).


20-29: The suggested optimization cannot be implemented due to model constraints.

The EntityMember.member ForeignKey uses related_name="+" (line 63 of backend/apps/owasp/models/entity_member.py), which explicitly disables the reverse relation from User to EntityMember. The current implementation using id__in with a subquery is the appropriate approach and cannot be optimized using the suggested direct join pattern.

Likely an incorrect or invalid review comment.

backend/tests/apps/nest/management/commands/nest_update_project_leader_badges_test.py (3)

13-16: LGTM! Clean metadata validation test.

The test directly verifies the command's badge metadata configuration. Simple and effective.


18-44: Test appropriately validates command execution with mocked dependencies.

The test properly mocks all external dependencies and verifies the command runs successfully with expected output. The extensive mocking is appropriate for a unit test that focuses on command integration rather than business logic (which is tested elsewhere in the base class tests).


46-59: LGTM! Error handling test properly validates exception propagation.

The test correctly simulates a failure in get_eligible_users and verifies that the exception propagates as expected. This validates the error handling behavior defined in BaseBadgeCommand.

@sonarqubecloud
Copy link

@arkid15r arkid15r added this pull request to the merge queue Jan 10, 2026
Merged via the queue into OWASP:main with commit 6471c8b Jan 10, 2026
27 checks passed
hussainjamal760 pushed a commit to hussainjamal760/Nest that referenced this pull request Jan 14, 2026
…, and add tests (OWASP#3040)

* update nest badges code

* update tests in nest badge

* apply coderabbit suggestions

* Refactor badges with base command

* resolve sonarcloud issues

* Update code

* apply bulk save and pluralize log messages

* update code

* Update code

* Update code

---------

Co-authored-by: Arkadii Yakovets <arkadii.yakovets@owasp.org>
Co-authored-by: Arkadii Yakovets <2201626+arkid15r@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor badges update job: modularize handlers, introduce base class, and add comprehensive tests

2 participants

Comments